//Brian Goodacre
//Parallel and Distributed Final Project

#include "file_sync.h"

//main function that executes everything
int main(int argc, char **argv){
	//MPI Variables
	int MyProc, num_threads;
	MPI_Status status;
	MPI_Request request;
	MPI_Init(&argc,&argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &MyProc);
	MPI_Comm_size(MPI_COMM_WORLD, &num_threads);
	MPI_Barrier(MPI_COMM_WORLD);
	//end MPI Variables		

	int i,j;
	i=0; j=0;
	//general information for all the threads
	int num_computers = num_threads-2; //exclude server and command line
	//Error Check
	if(num_threads<3){
		printf("Error. Please use 3 or more threads.\n");
		return 0; 
	}
		
	//command line interface thread
	if(MyProc==0){
		int kill, scanf_error,data,choice2,choice3,choice4;
		int connected_to_the_internet[num_computers];
		for(i=0;i<num_computers;i++) connected_to_the_internet[i]=1;//all connected
		kill=0; scanf_error=9;
		MPI_Barrier(MPI_COMM_WORLD);//every other thread is ready

		//Command Line Interface is now in charge =) =) =) =) =) =) =) =) =) =) =) =) 
		int choice=-1; 
		printf("----START TEXT INPUT----\n");
		
		//running the command line interface as if there were entered data
		while(choice!=0){
			sleep(1);//let everything sync up
			//print menu and get user input
			print_command_line_menu();
			choice=-1;//reset for loop
			do{
				printf("choice: ");
				fflush(stdin);
				scanf_error = scanf("%d",&choice);
				fflush(stdin);
			} while(choice==-1);
			
			//SWITCH statement to choose the appropriate path
			switch (choice){
				//end simulation. kill all threads
				case 0: 
					kill:
					//note, a broadcast does not use tags, so many things would break down. I need tags!
					kill=1;//not actually used
					for(i=1;i<num_threads;i++){
						MPI_Send(&kill,1,MPI_INT,i,KILL_TAG,MPI_COMM_WORLD);
					}
					break;
				//print the server's data
				case 1:
					MPI_Send(&choice,1,MPI_INT,server_process,COMMAND_PRINT_SERVER_DATA_TAG,MPI_COMM_WORLD);//send
					MPI_Recv(&data, 1, MPI_INT,server_process,COMMAND_PRINT_SERVER_DATA_DONE_TAG,MPI_COMM_WORLD,&status);//wait for a response so command printing does not mess up
					break;
				//print a computer's data
				case 2:
					choice2=-1;
					//which computer or all?
					print_command_view_computer_data(num_computers);
					choice2 = get_input();//get input
					//print depending on the case
					if(choice2==0){//print everything
						for(i=2;i<num_computers+2;i++){//send messages to all processes to print their data
							MPI_Send(&choice2,1,MPI_INT,i,COMMAND_PRINT_COMPUTER_DATA_TAG,MPI_COMM_WORLD);
							MPI_Recv(&data, 1, MPI_INT,i,COMMAND_PRINT_COMPUTER_DATA_DONE_TAG,MPI_COMM_WORLD,&status);
						}
					}
					else{//print a certain threads information
						MPI_Send(&choice2,1,MPI_INT,choice2,COMMAND_PRINT_COMPUTER_DATA_TAG,MPI_COMM_WORLD);
						MPI_Recv(&data, 1, MPI_INT,choice2,COMMAND_PRINT_COMPUTER_DATA_DONE_TAG,MPI_COMM_WORLD,&status);
					}
					break;
				//edit data via a remote machine through the server
				case 3:
					choice2=-1; choice3=-1; choice4=-1;
					print_command_edit_server_data(num_computers);//menu
					choice2 = get_input();
					if (choice2==0){//randomize all computer's data on the server
						printf("\t\tPlease enter how many values to randomize: (1,%d)\n\t",default_storage_array_size);
						choice3=get_input();
						MPI_Send(&choice3,1,MPI_INT,server_process,COMMAND_RANDOMIZE_SERVER_ALL_DATA_TAG,MPI_COMM_WORLD);//send message
						MPI_Recv(&data, 1, MPI_INT,server_process,COMMAND_RANDOMIZE_SERVER_ALL_DATA_DONE_TAG,MPI_COMM_WORLD,&status);//receive confirmation
					}
					else{ //randomize specific's data on the server
						printf("\t\tPlease enter how many values to randomize: (1,%d)\n\t",default_storage_array_size);
						choice4=get_input();
						MPI_Send(&choice2,1,MPI_INT,server_process,COMMAND_RANDOMIZE_SERVER_ALL_DATA_TAG,MPI_COMM_WORLD);//send information to computer
						MPI_Recv(&data, 1, MPI_INT,server_process,COMMAND_RANDOMIZE_SERVER_ONE_DATA_MORE_TAG,MPI_COMM_WORLD,&status);
						MPI_Send(&choice4,1,MPI_INT,server_process,COMMAND_RANDOMIZE_SERVER_ALL_DATA_TAG,MPI_COMM_WORLD);//send information to computer
						MPI_Recv(&data, 1, MPI_INT,server_process,COMMAND_RANDOMIZE_SERVER_ALL_DATA_DONE_TAG,MPI_COMM_WORLD,&status);//wait for a response
					}
					
					break;
				//edit a computer's data
				case 4:
					choice2=-1; choice3=-1; choice4=-1;
					print_command_edit_computer_data(num_computers);//menu
					choice2 = get_input();
					if (choice2==0){//randomize all computer's data
						printf("\t\tPlease enter how many values to randomize: (1,%d)\n\t",default_storage_array_size);
						choice3=get_input();
						for(i=2;i<num_computers+2;i++){//send messages to all processes to print their data
							MPI_Send(&choice3,1,MPI_INT,i,COMMAND_RANDOMIZE_COMPUTER_DATA_TAG,MPI_COMM_WORLD);
							MPI_Recv(&data, 1, MPI_INT,i,COMMAND_RANDOMIZE_COMPUTER_DATA_DONE_TAG,MPI_COMM_WORLD,&status);
						}
					}
					else{ //randomize specific's data
						printf("\t\tPlease enter how many values to randomize: (1,%d)\n\t",default_storage_array_size);
						choice4=get_input();
						MPI_Send(&choice4,1,MPI_INT,choice2,COMMAND_RANDOMIZE_COMPUTER_DATA_TAG,MPI_COMM_WORLD);//send information to computer
						MPI_Recv(&data, 1, MPI_INT,choice2,COMMAND_RANDOMIZE_COMPUTER_DATA_DONE_TAG,MPI_COMM_WORLD,&status);//wait for a response
					}
					break;
				//Disconnect a computer from the internet
				case 5:
					print_command_disconnect_menu(num_computers,connected_to_the_internet);//menu
					choice2=get_input();//input
					if(choice2==0){//disconnect all computers
						for(i=0;i<num_computers;i++){
							if(connected_to_the_internet[i]==1){
								connected_to_the_internet[i]=0;
								MPI_Send(&choice2,1,MPI_INT,i+2,COMMAND_DISCONNECT_FROM_INTERNET_TAG,MPI_COMM_WORLD);//send information to computer
							}
						}					
					}
					else {//disconnect one computer
						connected_to_the_internet[choice2-2]=0;//offline
						MPI_Send(&choice2,1,MPI_INT,choice2,COMMAND_DISCONNECT_FROM_INTERNET_TAG,MPI_COMM_WORLD);//send information to computer
					}
					break;
				//Connect to the internet
				case 6:
					print_command_connect_menu(num_computers,connected_to_the_internet);//menu
					choice2=get_input();//input
					if(choice2==0){//connect all computers
						for(i=0;i<num_computers;i++){
							if(connected_to_the_internet[i]==0){
								connected_to_the_internet[i]=1;
								MPI_Send(&choice2,1,MPI_INT,i+2,COMMAND_CONNECT_TO_INTERNET_TAG,MPI_COMM_WORLD);//send information to computer
							}
						}					
					}
					else {//connect one computer
						connected_to_the_internet[choice2-2]=1;//online
						MPI_Send(&choice2,1,MPI_INT,choice2,COMMAND_CONNECT_TO_INTERNET_TAG,MPI_COMM_WORLD);//send information to computer
					}
					break;
				//print the log 
				case 7:
					print_transcation_file();
					break;
				default:
					goto kill;
					break;
			}
		}
		printf("----END TEXT INPUT----\n");
	}
	
	//server thread
	else if(MyProc==server_process){
		//storage unit for the server
		int storage_matrix[num_computers][default_storage_array_size];
		int version_array[num_computers];
		int last_version_array[num_computers];
		int newer_version;
		int data1, data0;
		int temp_data[default_storage_array_size];
		
		//initialize storage_matrix
		for(i=0;i<num_computers;i++){
			for(j=0;j<default_storage_array_size;j++){
				storage_matrix[i][j]=0;		
			}
			version_array[i]=0;
			last_version_array[i]=0;
		}

		MPI_Barrier(MPI_COMM_WORLD);
		
		//run the server thread
		int keeprunning,proc,data,version;
		int buffer[default_storage_array_size];
		int buffer2[num_computers][default_storage_array_size];
		newer_version=0;
		
		keeprunning=1;
		while(keeprunning){
			//looking for any messages out there
			MPI_Probe(MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,&status);//blocking
			proc=status.MPI_SOURCE;
			//different tags for communication from the computer threads
			if(status.MPI_TAG==UPLOAD_START_TAG||status.MPI_TAG==UPLOAD_START_OLD_TAG){//server receives update
				//versioning is taken care of below in COMPUTER_VERSION_TAG
				MPI_Recv(&buffer, default_storage_array_size, MPI_INT, proc, UPLOAD_START_TAG, MPI_COMM_WORLD, &status);
				print_to_log(proc,version_array[proc-2],storage_matrix[proc-2],buffer);//write ahead log
				for(i=0;i<default_storage_array_size;i++){
					storage_matrix[proc-2][i]=buffer[i];
				}
			}
			else if(status.MPI_TAG==COMMAND_PRINT_SERVER_DATA_TAG){//print my data
				MPI_Recv(&data, 1, MPI_INT, command_line_process, COMMAND_PRINT_SERVER_DATA_TAG, MPI_COMM_WORLD, &status);//receive
				print_server_data(storage_matrix, num_threads-2, version_array);//print information
				MPI_Send(&data,1,MPI_INT,command_line_process,COMMAND_PRINT_SERVER_DATA_DONE_TAG,MPI_COMM_WORLD);
			}
			else if(status.MPI_TAG==KILL_TAG){//simulation over. exit
				MPI_Recv(&data, 1, MPI_INT,command_line_process,KILL_TAG,MPI_COMM_WORLD,&status);
				keeprunning=0;
			}
			else if(status.MPI_TAG==COMPUTER_VERSION_TAG){//check computer version
				MPI_Recv(&version, 1, MPI_INT, proc, COMPUTER_VERSION_TAG, MPI_COMM_WORLD, &status);
				//if the version is newer, ask for an upload
				if(version>version_array[proc-2]){
					version_array[proc-2]=version;//could potentially be bad in the future since it is updating early
					MPI_Send(&version,1,MPI_INT,proc,SERVER_UPLOAD_TAG,MPI_COMM_WORLD);//send
				}
			}
			else if(status.MPI_TAG==COMMAND_RANDOMIZE_SERVER_ALL_DATA_TAG){//randomize all storage matrix values
				MPI_Recv(&data, 1, MPI_INT,command_line_process,COMMAND_RANDOMIZE_SERVER_ALL_DATA_TAG,MPI_COMM_WORLD,&status);
				for(i=0;i<num_computers;i++) for(j=0;j<default_storage_array_size;j++) buffer2[i][j]=storage_matrix[i][j]; //copy
				randomize_matrix(storage_matrix,data,default_storage_array_size,num_computers);//randomize is easy
				//increase versions and log
				for(i=0;i<num_computers;i++){
					version_array[i]=version_array[i]+1;
					print_to_log(i+2,version_array[i],buffer2[i],storage_matrix[i]);//log
					MPI_Send(&i,1,MPI_INT,i+2,SERVER_REFRESH_TAG,MPI_COMM_WORLD);//refresh
				}
				
				MPI_Send(&MyProc,1,MPI_INT,command_line_process,COMMAND_RANDOMIZE_SERVER_ALL_DATA_DONE_TAG,MPI_COMM_WORLD);//confirm completion
			}
			else if(status.MPI_TAG==COMMAND_RANDOMIZE_SERVER_ONE_DATA_TAG){//randomize one row of the storage matrix
				MPI_Recv(&data0, 1, MPI_INT,command_line_process,COMMAND_RANDOMIZE_SERVER_ONE_DATA_TAG,MPI_COMM_WORLD,&status);//proc
				MPI_Send(&MyProc,1,MPI_INT,command_line_process,COMMAND_RANDOMIZE_SERVER_ONE_DATA_MORE_TAG,MPI_COMM_WORLD);
				MPI_Recv(&data1, 1, MPI_INT,command_line_process,COMMAND_RANDOMIZE_SERVER_ONE_DATA_TAG,MPI_COMM_WORLD,&status);//num change
				printf("0: %d, 1: %d\n",data0,data1);
				//increase version
				version_array[data0]=version_array[data0]+1;
				for(i=0;i<default_storage_array_size;i++) buffer[i] = storage_matrix[proc-2][i];//copy
				randomize_matrix_array(storage_matrix,data1,default_storage_array_size,num_computers,data0);//randomize is easy??
				print_to_log(proc,version_array[proc-2],buffer,storage_matrix[proc-2]);//log
				MPI_Send(&MyProc,1,MPI_INT,command_line_process,COMMAND_RANDOMIZE_SERVER_ONE_DATA_DONE_TAG,MPI_COMM_WORLD);//confirm completion
			}			
			else if(status.MPI_TAG==SERVER_DOWNLOAD_REQUEST_TAG){//computer wants to update their version
				MPI_Recv(&data, 1, MPI_INT,proc,SERVER_DOWNLOAD_REQUEST_TAG,MPI_COMM_WORLD,&status);//receive
				//no need to check if the version is newer now
				//make copy of information
				for(i=0;i<default_storage_array_size;i++)
					temp_data[i]=storage_matrix[proc-2][i];
				MPI_Send(&temp_data,default_storage_array_size,MPI_INT,proc,SERVER_DOWNLOAD_TAG,MPI_COMM_WORLD);
			}
			else if(status.MPI_TAG==SERVER_VERSION_TAG){//process is requesting an update
				MPI_Recv(&data,1,MPI_INT,proc,SERVER_VERSION_TAG,MPI_COMM_WORLD,&status);
				if(data<version_array[proc-2]){//computer needs to be updated
					version=version_array[proc-2];
					MPI_Send(&version,1,MPI_INT,proc,SERVER_VERSION_TAG,MPI_COMM_WORLD);//send the version
					//keep doing other things until it gets back with SERVER_DOWNLOAD_REQUEST_TAG
				}
			}
			else if(status.MPI_TAG==COMMAND_SYNC2_TAG){
				MPI_Recv(&data,1,MPI_INT,proc,SERVER_VERSION_TAG,MPI_COMM_WORLD,&status);
				version=version_array[proc-2];
				if(data<version_array[proc-2]){//computer needs to be updated
					MPI_Send(&version,1,MPI_INT,proc,SERVER_VERSION_TAG,MPI_COMM_WORLD);//send the version
					//keep doing other things until it gets back with SERVER_DOWNLOAD_REQUEST_TAG
				}
				else{//server needs to be updated
					version_array[proc-2]=data;
					MPI_Send(&version,1,MPI_INT,proc,SERVER_UPLOAD_TAG,MPI_COMM_WORLD);//send the version
				}
			}
		}
	}
	
	//computer thread
	else{	
		int data;
		//personal storage unit
		int storage_array[default_storage_array_size];
		int version=0; int last_version=0;
		int connected_to_internet=1; //connected

		//fill this storage_array with fake information
		randomize_array(default_storage_array_size,storage_array,default_storage_array_size,MyProc);
		version++;
		
		//hold up until every thread is initialized 
		MPI_Barrier(MPI_COMM_WORLD);
		
		//run the computer thread
		int proc;
		int keeprunning=1;
		while(keeprunning){
		
			//Computer sends notifications to the server when its files have been uploaded
			if(connected_to_internet==1&&last_version!=version){
				last_version=version;
				//non blocking send information to the server for the current version of the storage array
				MPI_Isend(&version,1,MPI_INT,server_process,COMPUTER_VERSION_TAG,MPI_COMM_WORLD,&request);
			}
			
			//Computers gets current version from server
			MPI_Isend(&version,1,MPI_INT,server_process,SERVER_VERSION_TAG,MPI_COMM_WORLD,&request);
			
			//look for information that is out there
			MPI_Probe(MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,&status);
			proc=status.MPI_SOURCE;
			//different scenarios for the computer threads to handle
			if(status.MPI_TAG==KILL_TAG){//simulation over. exit
				MPI_Recv(&data, 1, MPI_INT,command_line_process,KILL_TAG,MPI_COMM_WORLD,&status);
				keeprunning=0;
			}
			else if(status.MPI_TAG==COMMAND_UPLOAD_TAG){//need to upload to the server
				MPI_Recv(&data, 1, MPI_INT,command_line_process,COMMAND_UPLOAD_TAG,MPI_COMM_WORLD,&status);
				if(connected_to_internet==1){
					MPI_Send(&storage_array,default_storage_array_size,MPI_INT,server_process,UPLOAD_START_OLD_TAG,MPI_COMM_WORLD);//send the information to the server
				}
			}
			else if(status.MPI_TAG==SERVER_UPLOAD_TAG){
				MPI_Recv(&data, 1, MPI_INT,server_process,SERVER_UPLOAD_TAG,MPI_COMM_WORLD,&status);
				if(connected_to_internet==1){
					MPI_Send(&storage_array,default_storage_array_size,MPI_INT,server_process,UPLOAD_START_TAG,MPI_COMM_WORLD);
				}
			}
			else if(status.MPI_TAG==COMMAND_PRINT_COMPUTER_DATA_TAG){//server is ready, upload
				MPI_Recv(&data, 1, MPI_INT,command_line_process,COMMAND_PRINT_COMPUTER_DATA_TAG,MPI_COMM_WORLD,&status);
				print_computer_data(storage_array, default_storage_array_size, MyProc,version);//print information
				MPI_Send(&MyProc,1,MPI_INT,command_line_process,COMMAND_PRINT_COMPUTER_DATA_DONE_TAG,MPI_COMM_WORLD);//confirm completion
			}
			else if(status.MPI_TAG==COMMAND_RANDOMIZE_COMPUTER_DATA_TAG){
				MPI_Recv(&data, 1, MPI_INT,command_line_process,COMMAND_RANDOMIZE_COMPUTER_DATA_TAG,MPI_COMM_WORLD,&status);
				randomize_array(data,storage_array,default_storage_array_size,MyProc);//randomize data
				version++;
				MPI_Send(&MyProc,1,MPI_INT,command_line_process,COMMAND_RANDOMIZE_COMPUTER_DATA_DONE_TAG,MPI_COMM_WORLD);//confirm completion
			}
			else if(status.MPI_TAG==SERVER_REFRESH_TAG){
				MPI_Recv(&data, 1, MPI_INT, server_process, SERVER_REFRESH_TAG, MPI_COMM_WORLD, &status);
			}
			else if(status.MPI_TAG==SERVER_VERSION_TAG){
				MPI_Recv(&data, 1, MPI_INT,server_process,SERVER_VERSION_TAG,MPI_COMM_WORLD,&status);
				if(connected_to_internet==1){
					version=data;//update version
					MPI_Send(&version,1,MPI_INT,server_process,SERVER_DOWNLOAD_REQUEST_TAG,MPI_COMM_WORLD);//send
				}
			}
			else if(connected_to_internet&&status.MPI_TAG==SERVER_DOWNLOAD_TAG){
				MPI_Recv(&storage_array,default_storage_array_size,MPI_INT,server_process,SERVER_DOWNLOAD_TAG,MPI_COMM_WORLD,&status);
				//version already updated
			}
			else if(status.MPI_TAG==COMMAND_DISCONNECT_FROM_INTERNET_TAG){//disconnect
				MPI_Recv(&data,1,MPI_INT,command_line_process,COMMAND_DISCONNECT_FROM_INTERNET_TAG,MPI_COMM_WORLD,&status);
				connected_to_internet=0;//offline
			}
			else if(status.MPI_TAG==COMMAND_CONNECT_TO_INTERNET_TAG){//connect
				MPI_Recv(&data,1,MPI_INT,command_line_process,COMMAND_CONNECT_TO_INTERNET_TAG,MPI_COMM_WORLD,&status);
				connected_to_internet=1;//offline
			}
			else if(status.MPI_TAG==COMMAND_SYNC_TAG){
				MPI_Recv(&data,1,MPI_INT,command_line_process,COMMAND_SYNC_TAG,MPI_COMM_WORLD,&status);//receive
				if(connected_to_internet==1){
					MPI_Send(&version,1,MPI_INT,server_process,COMMAND_SYNC2_TAG,MPI_COMM_WORLD);//send
				}
			}
		}
	}
	
	 
	
	//FINISHED
	MPI_Barrier(MPI_COMM_WORLD);	
	MPI_Finalize ();
	return 0;
}


